package io.github.uicdb.flame_driven.common.recipe;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import io.github.noeppi_noeppi.libx.crafting.CraftingHelper2;
import io.github.noeppi_noeppi.libx.crafting.IngredientStack;
import io.github.uicdb.flame_driven.ModUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.crafting.CraftingHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class SingleInputRecipe implements Recipe<Container> {
    private final IngredientStack in;
    private final List<Pair<ItemStack, Double>> outChoice;
    private final ResourceLocation id;
    private final int craftTime;
    private final List<Pair<ItemStack,Double>> chanceList;
    public SingleInputRecipe(IngredientStack in,
                             List<Pair<ItemStack, Double>> outChoice,
                             ResourceLocation id, int craftTime) {
        this.in = in;
        this.outChoice = outChoice;
        this.id = id;
        this.craftTime = craftTime;
        chanceList=ModUtil.modifyChanceList(outChoice);
    }

    @Override
    public boolean matches(@NotNull Container pContainer, @NotNull Level pLevel) {
        if (pContainer.isEmpty()) return false;
        ItemStack itemStack=pContainer.getItem(0);
        if (itemStack.getCount() > 64) return false;
        return in.test(itemStack) && ((itemStack.getCount()%in.count()) == 0);
    }

    @NotNull
    @Override
    public ItemStack assemble(Container pContainer) {
        var copy = getResultItem().copy();
        int count=pContainer.getItem(0).getCount() / in.count();
        copy.setCount(count);
        pContainer.removeItem(0,count*in.count());
        return copy;
    }

    @Override
    public boolean canCraftInDimensions(int pWidth, int pHeight) {
        return true;
    }

    @NotNull
    @Override
    public ItemStack getResultItem() {
        return ModUtil.luckDraw(chanceList,ItemStack.EMPTY).copy();
    }

    @NotNull
    @Override
    public ResourceLocation getId() {
        return id;
    }

    @NotNull
    @Override
    public RecipeSerializer<?> getSerializer() {
        return Serializer.INSTANCE;
    }

    @NotNull
    @Override
    public RecipeType<?> getType() {
        return ModRecipes.SINGLE_INPUT_RECIPE_RECIPE_TYPE;
    }

    public IngredientStack in() {
        return in;
    }

    public List<Pair<ItemStack, Double>> outChoice() {
        return outChoice;
    }

    public ResourceLocation id() {
        return id;
    }

    public int craftTime() {
        return craftTime;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (SingleInputRecipe) obj;
        return Objects.equals(this.in, that.in) &&
                Objects.equals(this.outChoice, that.outChoice) &&
                Objects.equals(this.id, that.id) &&
                this.craftTime == that.craftTime;
    }

    @Override
    public int hashCode() {
        return Objects.hash(in, outChoice, id, craftTime);
    }

    @Override
    public String toString() {
        return "SingleInputRecipe[" +
                "in=" + in + ", " +
                "outChoice=" + outChoice + ", " +
                "id=" + id + ", " +
                "craftTime=" + craftTime + ']';
    }


    public static class Serializer implements RecipeSerializer<SingleInputRecipe> {
        public static Serializer INSTANCE = new Serializer();

        @NotNull
        @Override
        public SingleInputRecipe fromJson(@NotNull ResourceLocation pRecipeId, @NotNull JsonObject pSerializedRecipe) {
            List<Pair<ItemStack, Double>> results = new ArrayList<>();
            JsonArray jsonArray = pSerializedRecipe.getAsJsonArray("Out");
            jsonArray.forEach(jsonElement -> {
                var obj = jsonElement.getAsJsonObject();
                results.add(new Pair<>(CraftingHelper.getItemStack(obj.getAsJsonObject("Item"), true), obj.get("Weight").getAsDouble()));
            });
            return new SingleInputRecipe(IngredientStack.fromJson(pSerializedRecipe.getAsJsonObject("In")), results, pRecipeId, pSerializedRecipe.get("CraftTimes").getAsInt());
        }

        @Nullable
        @Override
        public SingleInputRecipe fromNetwork(@NotNull ResourceLocation pRecipeId, @NotNull FriendlyByteBuf pBuffer) {
            var input = IngredientStack.fromNetwork(pBuffer);
            List<Pair<ItemStack, Double>> results = new ArrayList<>();
            for (int i = 0; i < pBuffer.readByte(); i++) {
                results.add(new Pair<>(pBuffer.readItem(), pBuffer.readDouble()));
            }
            var time = pBuffer.readInt();
            return new SingleInputRecipe(input, results, pRecipeId, time);
        }

        @Override
        public void toNetwork(@NotNull FriendlyByteBuf pBuffer, @NotNull SingleInputRecipe pRecipe) {
            pRecipe.in.toNetwork(pBuffer);
            pBuffer.writeByte(pRecipe.outChoice.size());
            pRecipe.outChoice.forEach(pair -> {
                pBuffer.writeItem(pair.getFirst());
                pBuffer.writeDouble(pair.getSecond());
            });
            pBuffer.writeInt(pRecipe.craftTime);
        }
        public JsonObject toJson(SingleInputRecipe recipe){
            JsonObject result=new JsonObject();
            result.addProperty("type",getRegistryName().toString());
            result.add("In",recipe.in.toJson());
            JsonArray outArray=new JsonArray();
            recipe.outChoice.forEach(pair->{
                JsonObject outJson=new JsonObject();
                outJson.add("Item",CraftingHelper2.serializeItemStack(pair.getFirst(),true));
                outJson.addProperty("Weight",pair.getSecond());
                outArray.add(outJson);
            });
            result.add("Out",outArray);
            result.addProperty("CraftTimes",recipe.craftTime);
            return result;
        }

        private ResourceLocation name;

        @Override
        public RecipeSerializer<?> setRegistryName(ResourceLocation name) {
            this.name = name;
            return this;
        }

        @Nullable
        @Override
        public ResourceLocation getRegistryName() {
            return this.name;
        }

        @Override
        public Class<RecipeSerializer<?>> getRegistryType() {
            return ModUtil.castClass(SingleInputRecipe.class);
        }
    }
}
